Skip to content

feat(custom):新增 screenshot_on_fail.py节点级截图sink - #14

Merged
kqcoxn merged 1 commit into
MaaXYZ:mainfrom
BQOvO:main
Aug 25, 2026
Merged

feat(custom):新增 screenshot_on_fail.py节点级截图sink#14
kqcoxn merged 1 commit into
MaaXYZ:mainfrom
BQOvO:main

Conversation

@BQOvO

@BQOvO BQOvO commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

解决的问题

在 MaaFramework 开发中,很多开发者会把业务逻辑写在 on_error 里,导致 Pipeline 节点真正失败时无法触发默认截图。用户反馈 bug 时,开发者拿不到失败截图,只能靠日志盲猜,排查效率很低。

Screenshot On Fail 是一个节点级 ContextEventSink,它绕过 on_error 机制,直接监听每个 Pipeline 节点的识别完成事件——无论成功还是失败都截图,确保开发者永远能拿到完整的运行现场。

核心特性

  • 节点级监听:每个识别节点完成时自动触发,不依赖 on_error
  • 全量截图:成功/失败都保存,完整还原运行过程
  • JPG 输出:优先使用 cv2 编码为 JPG(质量 85),不可用时自动回退 BMP
  • 环形清理:最多保留 300 张,超出自动删除旧图,防止磁盘写满
  • 文件名自描述时间戳_节点名_识别ID_状态.jpg,一眼定位问题节点

文件结构

  • maahub_meta.json — 元信息
  • README.md — 详细说明
  • main.py — 入口,导入即注册
  • screenshot_on_fail.py — Sink 核心实现
  • pipeline.json — 空配置(Sink 无需 pipeline)

依赖

  • numpy
  • opencv-python(可选,不可用时回退 BMP)

Sourcery 摘要

新增节点级诊断截图接收器,用于记录成功和失败识别的 Pipeline 执行上下文。

新功能:

  • 新增节点级截图接收器,在 Pipeline 节点完成后捕获图像,无论执行成功还是失败。
  • 使用包含描述性时间戳、节点名称、识别 ID 和状态的文件名保存诊断截图;支持 JPG 时使用 JPG,否则回退为 BMP。

增强功能:

  • 将存储的截图数量限制为 300 个文件,并删除较早的截图,以防止磁盘使用量无限增长。
  • 允许通过 MDNA_DEBUG_DIR 环境变量自定义调试输出目录。

文档:

  • 为 Screenshot On Fail 接收器新增使用方法、输出路径、依赖项和集成文档。
Original summary in English

Summary by Sourcery

Add a node-level diagnostic screenshot sink that records Pipeline execution context for both successful and failed recognitions.

New Features:

  • Add a node-level screenshot sink that captures images after Pipeline nodes complete, regardless of success or failure.
  • Save diagnostic screenshots with descriptive timestamps, node names, recognition IDs, and statuses, using JPG when available and BMP as a fallback.

Enhancements:

  • Limit stored screenshots to 300 files and remove older captures to prevent unbounded disk usage.
  • Allow the debug output directory to be customized through the MDNA_DEBUG_DIR environment variable.

Documentation:

  • Add usage, output path, dependency, and integration documentation for the Screenshot On Fail sink.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

嘿——我发现了 3 个问题

面向 AI Agent 的提示
请处理此次代码审查中的评论:

## 各条评论

### 评论 1
<location path="Storage/customs/BQ/screenshot-on-fail/screenshot_on_fail.py" line_range="130" />
<code_context>
+        if noti_type in (NotificationType.Succeeded, NotificationType.Failed):
</code_context>
<issue_to_address>
**issue (bug_risk):** sink 会为每种通知类型保存截图,但只有在 `Succeeded``Failed` 情况下才会获取节点专属的识别数据。当回调收到 `Starting` 时,它会回退使用 `cached_image`,并将生成的文件标记为 `failed`,从而为尚未完成的识别事件生成具有误导性的失败截图。

**触发条件:**`on_node_pipeline_node` 发出其正常的 `Starting` 通知时。

**建议修复:** 除非 `noti_type``NotificationType.Succeeded``NotificationType.Failed`,否则直接返回;或者为 `Starting` 处理其自身的状态和图像语义。

```suggestion
        if noti_type not in (NotificationType.Succeeded, NotificationType.Failed):
            return

        node_name = detail.name or "unknown"
```
</issue_to_address>

### 评论 2
<location path="Storage/customs/BQ/screenshot-on-fail/screenshot_on_fail.py" line_range="158-166" />
<code_context>
+        filename = f"{timestamp}_{node_name}_{reco_id}_{status}.jpg"
+        filepath = dirpath / filename
+
+        _save_image(img, filepath)
+        _cleanup_old_screenshots()
\ No newline at end of file
</code_context>
<issue_to_address>
**issue (bug_risk):** 图像编码、文件创建和清理过程中的失败没有在回调边界处进行处理。`cv2.imencode``open``mkdir``unlink``_cleanup_old_screenshots` 可能抛出异常,导致 context sink 回调失败,而不是仅报告调试截图无法保存。

**触发条件:** 调试目录不可写、磁盘已满、截图使用了不受支持的数据类型/形状,或清理操作与其他文件系统变更发生竞争时。

**建议修复:** 使用异常处理包裹保存和清理操作,记录失败,并确保截图诊断不会中断任务执行。

```suggestion
        try:
            dirpath = Path(_SCREENSHOT_DIR)
            dirpath.mkdir(parents=True, exist_ok=True)
            timestamp = datetime.now().strftime("%Y.%m.%d-%H.%M.%S.%f")[:-3]
            status = "success" if noti_type == NotificationType.Succeeded else "failed"
            filename = f"{timestamp}_{node_name}_{reco_id}_{status}.jpg"
            filepath = dirpath / filename

            _save_image(img, filepath)
            _cleanup_old_screenshots()
        except Exception:
            _log.exception("保存调试截图失败")
```
</issue_to_address>

### 评论 3
<location path="Storage/customs/BQ/screenshot-on-fail/screenshot_on_fail.py" line_range="70" />
<code_context>
+            f.write(encoded.tobytes())
+        return True
+
+    return _save_bmp(img, filepath.with_suffix(".bmp"))
+
+
</code_context>
<issue_to_address>
**nitpick (bug_risk):** 回退逻辑会写入 `filepath.with_suffix(".bmp")`,而调用方仍继续使用并记录原始的 `.jpg` 路径。因此,文档中描述的输出模式与实际的回退输出不一致;任何期望获得返回的 `.jpg` 文件名的调用方或工具都无法发现生成的 BMP 文件。

**触发条件:** cv2 不可用时。

**建议修复:**`_save_image` 返回实际的输出路径,在写入前构造扩展名,并使 README/文档字符串中的文件名模式与回退行为保持一致。
</issue_to_address>

Sourcery 对开源项目免费提供服务——如果你喜欢我们的审查,请考虑分享给他人 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会利用你的反馈来改进审查结果。
Original comment in English

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="Storage/customs/BQ/screenshot-on-fail/screenshot_on_fail.py" line_range="130" />
<code_context>
+        if noti_type in (NotificationType.Succeeded, NotificationType.Failed):
</code_context>
<issue_to_address>
**issue (bug_risk):** The sink saves a screenshot for every notification type, but only retrieves node-specific recognition data for `Succeeded` and `Failed`. When the callback receives `Starting`, it falls back to `cached_image` and labels the resulting file `failed`, producing a misleading failure screenshot for an incomplete recognition event.

**Triggers:** When `on_node_pipeline_node` emits its normal `Starting` notification.

**Suggested fix:** Return unless `noti_type` is `NotificationType.Succeeded` or `NotificationType.Failed`, or handle `Starting` with its own status and image semantics.

```suggestion
        if noti_type not in (NotificationType.Succeeded, NotificationType.Failed):
            return

        node_name = detail.name or "unknown"
```
</issue_to_address>

### Comment 2
<location path="Storage/customs/BQ/screenshot-on-fail/screenshot_on_fail.py" line_range="158-166" />
<code_context>
+        filename = f"{timestamp}_{node_name}_{reco_id}_{status}.jpg"
+        filepath = dirpath / filename
+
+        _save_image(img, filepath)
+        _cleanup_old_screenshots()
\ No newline at end of file
</code_context>
<issue_to_address>
**issue (bug_risk):** Failures from image encoding, file creation, and cleanup are not handled at the callback boundary. `cv2.imencode`, `open`, `mkdir`, `unlink`, or `_cleanup_old_screenshots` can raise, causing the context sink callback to fail instead of merely reporting that a debug screenshot could not be saved.

**Triggers:** When the debug directory is unwritable, the disk is full, a screenshot has an unsupported dtype/shape, or cleanup races with another filesystem change.

**Suggested fix:** Wrap saving and cleanup in exception handling, log the failure, and ensure screenshot diagnostics cannot interrupt task execution.

```suggestion
        try:
            dirpath = Path(_SCREENSHOT_DIR)
            dirpath.mkdir(parents=True, exist_ok=True)
            timestamp = datetime.now().strftime("%Y.%m.%d-%H.%M.%S.%f")[:-3]
            status = "success" if noti_type == NotificationType.Succeeded else "failed"
            filename = f"{timestamp}_{node_name}_{reco_id}_{status}.jpg"
            filepath = dirpath / filename

            _save_image(img, filepath)
            _cleanup_old_screenshots()
        except Exception:
            _log.exception("保存调试截图失败")
```
</issue_to_address>

### Comment 3
<location path="Storage/customs/BQ/screenshot-on-fail/screenshot_on_fail.py" line_range="70" />
<code_context>
+            f.write(encoded.tobytes())
+        return True
+
+    return _save_bmp(img, filepath.with_suffix(".bmp"))
+
+
</code_context>
<issue_to_address>
**nitpick (bug_risk):** The fallback writes to `filepath.with_suffix(".bmp")`, while the caller continues to use and document the original `.jpg` path. The documented output pattern therefore does not match the actual fallback output, and any caller or tooling expecting the returned `.jpg` filename has no way to discover the generated BMP file.

**Triggers:** When cv2 is unavailable.

**Suggested fix:** Return the actual output path from `_save_image`, construct the extension before writing, and keep the README/docstring filename pattern consistent with the fallback behavior.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread Storage/customs/BQ/screenshot-on-fail/screenshot_on_fail.py
Comment thread Storage/customs/BQ/screenshot-on-fail/screenshot_on_fail.py
Comment thread Storage/customs/BQ/screenshot-on-fail/screenshot_on_fail.py
@kqcoxn
kqcoxn merged commit cbfe5a3 into MaaXYZ:main Aug 25, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants